/*
 * EvoAlghorithm.cpp
 *
 *	Reprezentuje evoluční algoritmus.
 *  Created on: 15.11.2012
 *      Author: Lukas Gemela
 */

#include "EvoAlghorithm.h"
#include <algorithm>
#include "tbb/parallel_for.h"
#include "FitnessComputer.h"
#include <sys/time.h>
#include "tbb/mutex.h"

EvoAlghorithm::EvoAlghorithm(const shared_ptr<IFastDataApprox> &BGValues,
		const shared_ptr<IFastDataApprox> &IGValues,
		const shared_ptr<vector<double>> p_ECGValues, INPUT_PARAMS params) :
		p_BGValues(BGValues), p_IGValues(IGValues), p_ECGValues(p_ECGValues), params(
				params), checker(Checker(params)) {
	srand(time(NULL) + getpid());

}

/**
 * Provádí mutaci.
 */
void EvoAlghorithm::mutate(double& mutationRate, int& iInd,
		LINDIVIDUUM population) {
	//mutace
	for (int child = 0; child < 2; child++) {
		for (int i = 0; i < PARAMETER_COUNT; i++) {
			//pokud je P nebo C přednastaveno z konzole, nepokoušíme
			//se je mutovat
			if ((i == PI && params.isPset) || (i == CI && params.isCset))
				continue;

			if (random() < mutationRate) {
				//Box-Muller transform
				double randomValue = 0.25 * sqrt(-2 * log(random(0, 1)))
						* sin(2 * M_PI * random(0, 1));
				randomValue += getIndividuumValue(&population[iInd + child], i);
				if (checker.checkRange(i, randomValue)) {
					updateIndividuum(&population[iInd + child], i, randomValue);
				}
			}
		}
	}
}
/**
 * Vrací hodnotu ze struktury INDIVIDUUM podle indexu i.
 */
double EvoAlghorithm::getIndividuumValue(INDIVIDUUM *individuum, int i) {

	switch (i) {
	case KI:
		return individuum->k;
		break;
	case PI:
		return individuum->p;
		break;
	case CGI:
		return individuum->cg;
		break;
	case CI:
		return individuum->c;
		break;
	case DTI:
		return individuum->dt;
		break;
	default:
		std::cout << "Out of individuum struct" << endl;
		return -1;
		break;
	}
}

/**
 * Přiřadí hodnotu do struktury podle indexu i.
 */
void EvoAlghorithm::updateIndividuum(INDIVIDUUM *individuum, int i,
		double value) {

	switch (i) {
	case KI:
		individuum->k = value;
		break;
	case PI:
		individuum->p = value;
		break;
	case CGI:
		individuum->cg = value;
		break;
	case CI:
		individuum->c = value;
		break;
	case DTI:
		individuum->dt = value;
		break;
	default:
		std::cout << "Out of individuum struct" << endl;
		break;
	}
}

/**
 * Nastřelí geny populace podle zadaných parametrů
 */
void EvoAlghorithm::initializePopulation() {
	//inicializujeme populaci
	for (int i = 0; i < params.populationSize; i++) {

		//Pokud je p,c nastaveno přiřaď jeho hodnotu, jinak
		//vygeneruj
		population[i].p = (
				params.isPset ?
						params.p :
						random(PARAM_P_DEFAULT_MIN, PARAM_P_DEFAULT_MAX));
		population[i].cg = random(PARAM_CG_DEFAULT_MIN, PARAM_CG_DEFAULT_MAX);
		population[i].c = (
				params.isCset ?
						params.c :
						random(PARAM_C_DEFAULT_MIN, PARAM_C_DEFAULT_MAX));

		double k = 0;
		double dt = 0;
		findKandDT(&k, &dt);
		population[i].dt = dt;
		population[i].k = k;
	}
}

/**
 * Spustí výpočet, je vrácen nejlepší nalezený jedinec.
 */
INDIVIDUUM EvoAlghorithm::compute(LPAPPROXPOINT bgAPPROXPOINTS,
		LPAPPROXPOINT igAPPROXPOINTS, int len, double *average, double *max) {

	m_fitness = new double[params.populationSize];
	for (int i = 0; i < params.populationSize; i++) {
		m_fitness[i] = 0.0;
	}

	population = new INDIVIDUUM[params.populationSize];
	INDIVIDUUM *newPopulation = new INDIVIDUUM[params.populationSize];


	double mutationRate = params.mutratemin;
	double finalTime = 0;
	//inicializujeme populaci
	initializePopulation();

	double previousFitness = 0.0f;
	tbb::mutex mutex;
	for (int iGeneration = 0; iGeneration < params.generation; iGeneration++) {
		double bestFitness = 0.0f;
		int bestFitnessIdx = 0;
		double minFitness = numeric_limits<double>::infinity();

		struct timespec start, finish;
		double elapsed;

		clock_gettime(CLOCK_MONOTONIC, &start);
		THREAD_RESULTS result = {0, -1, numeric_limits<double>::infinity()};

		if (params.threadCount > 1) {
			//TBB VYPOCET
			tbb::parallel_for(
					tbb::blocked_range<int>(0, params.populationSize, params.step),
					FitnessComputer(bgAPPROXPOINTS, igAPPROXPOINTS, len,
							population, m_fitness, p_BGValues, p_IGValues,
							p_ECGValues, params, &result, &mutex));

		} else {
			//SERIOVY VYPOCET
			FitnessComputer computer(bgAPPROXPOINTS, igAPPROXPOINTS, len,
					population, m_fitness, p_BGValues, p_IGValues, p_ECGValues,
					params, &result, &mutex);
			computer.serial(0, params.populationSize);
		}
		clock_gettime(CLOCK_MONOTONIC, &finish);
		elapsed = (finish.tv_sec - start.tv_sec);
		elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;
		finalTime += elapsed;

		bestFitnessIdx = result.bestFitnessIdx;
		bestFitness = result.bestFitness;
		minFitness = result.minFitness;

// upravime hodnoty fitness pro lepsi selekci od 0 do bestFitness-minFitness
		double sumFitness = 0.0;
		for (int i = 0; i < params.populationSize; i++) {
			m_fitness[i] -= minFitness;
			sumFitness += m_fitness[i];
		}

		// vytvoreni nove populace, ktera nahradi stavajici
		for (int iInd = 0; iInd < params.populationSize; iInd += 2) {
			int individuumIdx1 = pickRandomIndividuum(sumFitness);
			int individuumIdx2 = pickRandomIndividuum(sumFitness);
			//prirozeny vyber
			if (random() < params.crossrate) {
				for (int i = 0; i < PARAMETER_COUNT; i++) {
					if (random() < 0.5) {
						updateIndividuum(&newPopulation[iInd], i,
								getIndividuumValue(&population[individuumIdx1],
										i));
						updateIndividuum(&newPopulation[iInd + 1], i,
								getIndividuumValue(&population[individuumIdx2],
										i));
					} else {
						updateIndividuum(&newPopulation[iInd], i,
								getIndividuumValue(&population[individuumIdx2],
										i));
						updateIndividuum(&newPopulation[iInd + 1], i,
								getIndividuumValue(&population[individuumIdx1],
										i));
					}
				}
				//mutace
				mutate(mutationRate, iInd, newPopulation);

			} else { //neni krizeni
				for (int i = 0; i < PARAMETER_COUNT; i++) {
					updateIndividuum(&newPopulation[iInd], i,
							getIndividuumValue(&population[individuumIdx1], i));
					updateIndividuum(&newPopulation[iInd + 1], i,
							getIndividuumValue(&population[individuumIdx2], i));
				}
			}
		}

		//elitismus - zachování nejlepšího
			newPopulation[0] = population[bestFitnessIdx];

			cout << "Generation " << iGeneration << ", best fitness " << bestFitness
				<< endl;

		std::copy(newPopulation, newPopulation + params.populationSize,
				population);
		if (bestFitness > previousFitness) {
			previousFitness = bestFitness;
			mutationRate = params.mutratemin;
			//V první generaci začít s nejnižší mutací a v každé následující generaci mutaci zvýšit.
			//Mutace se znova vrátí na nejnižší hodnotu v případě, že v nové generaci je nalezen lepší jedinec
			//než v předchozí anebo v případě, že mutace dosáhla své maximální hodnoty.
			//
			//
		} else {
			if (mutationRate >= params.mutratemax)
				mutationRate = params.mutratemin;
			else
				mutationRate += params.mutstep;
		}
	}
	findMaxAndAverageDiff(bgAPPROXPOINTS, igAPPROXPOINTS, population[0], average, max, len);
	cout << "p = " << population[0].p << endl;
	cout << "cg = " << population[0].cg << endl;
	cout << "c = " << population[0].c << endl;
	cout << "k = " << population[0].k << endl;
	cout << "dt = " << population[0].dt << endl;
	cout << "Time Difference: " << finalTime << endl;
	delete [] newPopulation;

	INDIVIDUUM ret = population[0];

	return ret;
}

/**
 * Implementace roulette-wheel selection
 */
int EvoAlghorithm::pickRandomIndividuum(double sumFitness) {
	double sum = 0.0;
	double fitnessPoint = random() * sumFitness;
	for (int i = 0; i < params.populationSize; i++) {
		sum += m_fitness[i];
		if (sum > fitnessPoint)
			return i;
	}
	return 0;
}

/**
 * do average načte průměrnou absolutní odchylku naměřené a vypočítané i(t)
 * do max načte maximální odchylku
 */
void EvoAlghorithm::findMaxAndAverageDiff(LPAPPROXPOINT bgAPPROXPOINTS,
		LPAPPROXPOINT igAPPROXPOINTS, INDIVIDUUM individuum, double *average,
		double *max, int len) {
	double localSum = 0;
	double maxDiff = 0;
	int localCount = 0;
	for (int i = 0; i < len; i++) {

		int igPoint = 0;
		double ecg = (*p_ECGValues)[i];
		p_BGValues->GetPrecedingPoint(
				TimeUtils::convertSecToWin(i) + individuum.dt
						+ individuum.k * individuum.dt * ecg, &igPoint);

		if (igPoint < len && igPoint > 0) {

			double dif = fabs(
					individuum.p * bgAPPROXPOINTS[i].y
							+ individuum.cg * bgAPPROXPOINTS[i].y * ecg
							+ individuum.c - igAPPROXPOINTS[igPoint].y);
			if (dif > maxDiff) {
				maxDiff = dif;
			}
			localCount++;
			localSum += dif;
		}

	}
	*average = localSum / (double) localCount;
	*max = maxDiff;
}

/**
 * Vrací náhodné číslo v rozmezí (0,1)
 */
double EvoAlghorithm::random() {
	return ((double) rand() / (double) RAND_MAX);
}

/**
 * Vrací náhodné číslo v rozmezí (min,max)
 */
double EvoAlghorithm::random(double min, double max) {
	double r = random();
	return min + r * (max - min);
}

/**
 * Náhodně vygeneruje K a dt tak, aby odpovídal zadaným podmínkám
 */
void EvoAlghorithm::findKandDT(double *k, double *dt) {
	vector<double>::const_iterator it;

	double found = true;
	double localK;
	double localDt;
	while (found) {
		if (params.isKset) {
			localK = random(params.kMin, params.kMax);
		} else {
			localK = random();
		}
		localDt = random(params.dtMin, params.dtMax);

		for (unsigned i = 0; i < p_ECGValues->size(); i++) {
			found = !checker.checkTimeJumpRange(localK, localDt,
					(*p_ECGValues)[i]);
			if (found)
				break;
		}
	}
	*k = localK;
	*dt = localDt;

}

EvoAlghorithm::~EvoAlghorithm() {
	delete [] population;
	delete [] m_fitness;
}
